package rolesystem.jade.server;

import jade.core.*;
import jade.core.behaviours.*;
import jade.lang.acl.*;

import rolesystem.jade.*;
import rolesystem.core.*;

import java.io.Serializable;

/**
 * This is the behaviour of the server agent.
 * It manages a rolesystem, serving all the messages received.
 */
public class ServerBehaviour extends CyclicBehaviour
{
	/**
	 * Maximum number of registrations that can be assigned
	 * by this server.
	 */
	private transient int maxRegs;

	/**
	 * Array of registrations.
	 * Position 0 is reserved.
	 */
	private transient Registration[] registrations;

	/**
	 * Permission matrix of the server.
	 */
	private transient PermissionMatrix permissionMatrix;

	/**
	 * Object to synchronize on.
	 */
	private transient Object mutex;


	/**
	 * @param agent Agent owner of this behaviour.
	 * @param permissionMatrix Permission matrix of the server.
	 * @param maxRegs Maximum number of registrations that can be assigned
	 * by this server.
	 * @param permissionMatrix Permission matrix of the server.
	 */
	public ServerBehaviour(Agent agent, PermissionMatrix permissionMatrix,
		int maxRegs)
	{
		super(agent);
		this.permissionMatrix=permissionMatrix;
		this.maxRegs=maxRegs;
		registrations=new Registration[maxRegs];
		mutex=new Object();
	}


	public void action()
	{
		ACLMessage msg;

		msg = myAgent.receive();
		if (msg == null)
		{
			block();
		 	return;
		}
		new Thread(new MessageDispatcher(msg)).start();
	}


	/**
	 * Processes the received message.
	 * Its body is synchronized on an object, so only one message at a time
	 * can be actively processed; this is done because each processing of
	 * a message can cause internal modification, but along its execution
	 * it is assumed that no modification can occur from the outside
	 * (i.e. other threads).
	 * @param receivedMsg Received message to process.
	 */
	private void processMsg(ACLMessage receivedACL)
	{
		ACLMessage replyACL;
		RoleMsg receivedMsg, replyMsg;

		synchronized(mutex)
		{
			replyACL=receivedACL.createReply();
			try
			{
				receivedMsg=(RoleMsg)receivedACL.getContentObject();

				int scope=receivedMsg.getScope();
				try
				{
					if(scope==0)
					{
						replyMsg=new RoleMsg(scope,
							processSystemCommand((RoleCommand)receivedMsg.getContent(),
							receivedACL.getSender()));
					}
					else
					{
						processRegistrationCommand(scope, (RoleCommand)receivedMsg.getContent(),
							receivedACL.getSender());
						replyMsg=new RoleMsg(scope,null);
					}
					replyACL.setPerformative(ACLMessage.INFORM);
				}
				catch(RoleException re)
				{
					replyMsg=new RoleMsg(scope,re);
					replyACL.setPerformative(ACLMessage.FAILURE);
				}
				replyACL.setContentObject(replyMsg);
			}
			catch(Exception e)
			{
				System.err.println(e);
				replyACL.setPerformative(ACLMessage.NOT_UNDERSTOOD);
			}
			myAgent.send(replyACL);
		}
	}

	/**
	 * Processes the specified command, the scope of wich is the system.
	 * @param command Command to process.
	 * @param sender Agent-identifier of command sender.
	 * @return The content of reply.
	 */
	private Serializable processSystemCommand(RoleCommand command,
		AID sender) throws RoleException
	{
		try
		{
			if(command.getName().equals("reqRegistration"))
				return reqRegistration((String)command.getParameters()[0], sender);
			if(command.getName().equals("searchForRole"))
				if(command.getParameters().length==1)
					return searchForRole((String)command.getParameters()[0]);
				else
					return searchForRole((String)command.getParameters()[0],
						((Long)command.getParameters()[1]).longValue());
			if(command.getName().equals("searchForRoleNoWait"))
				return searchForRoleNoWait((String)command.getParameters()[0]);
		}
		catch(RoleException re)
		{
			throw re;
		}
		catch(Exception e)
		{
			System.err.println(e);
		}
		throw new RoleException();
	}
	
	
	/**
	 * Processes the specified command, the scope of wich is a
	 * registration.
	 * @param id Identifier of the registration, scope of the command.
	 * @param command Command to process.
	 * @param sender Agent-identifier of command sender.
	 * @return The content of reply.
	 */
	private void processRegistrationCommand(int id, RoleCommand command,
		AID sender) throws RoleException
	{
		try
		{
			Registration reg=registrations[id];
	
			if(reg!=null && reg.getAgent().equals(sender))
			{
				if(command.getName().equals("dismiss"))
				{
					dismiss(id);
					return;
				}
				if(command.getName().equals("doAction"))
				{
					doAction(id, reg, (RoleAction)command.getParameters()[0]);
					return;
				}
			}
		}
		catch(RoleException re)
		{
			throw re;
		}
		catch(Exception e)
		{
			System.err.println(e);
		}
		throw new RoleException();
	}


	/**
	 * Processes command "reqRegistration".
	 * @param role Role requested for registration.
	 * @param agent Agent-identifier of the agent the registration is
	 * going to be assigned to.
	 * @return The identifier assigned to the registration.
	 * @see rolesystem.core.RoleSystem#reqRegistration
	 */
	private Integer reqRegistration(String role, AID agent)
		throws RoleException
	{
		int count;

		for(count=1; count<maxRegs; count++)
		{
			if(registrations[count]==null)
				break;
		}
		if(count==maxRegs)
			throw new RoleException(RoleException.TOO_MANY);

		registrations[count]=new Registration(role, agent);
		mutex.notifyAll();
		return new Integer(count);
	}


	/**
	 * Processes command "searchForRole".
	 * @see rolesystem.core.RoleSystem#searchForRole(String role)
	 */
	private int[] searchForRole(String role)
		throws RoleException
	{
		int count, numReg;
		Registration tmp;
		int[] regArray;

		for(;;)
		{
			numReg=0;
			for(count=1; count<maxRegs; count++)
			{
				tmp=registrations[count];
				if((tmp!=null) && tmp.getRole().equals(role))
					numReg++;
			}
			if(numReg!=0)
				break;
			try
			{
				mutex.wait();
			}
			catch (InterruptedException ie)
			{
			}
		}
		regArray=new int[numReg];
		numReg=0;
		for(count=1; count<maxRegs; count++)
		{
			tmp=registrations[count];
			if((tmp!=null) && tmp.getRole().equals(role))
			{
				regArray[numReg]=count;
				numReg++;
			}
		}
		return regArray;
	}


	/**
	 * Processes command "searchForRole".
	 * @see rolesystem.core.RoleSystem#searchForRole(String role, long timeout)
	 */
	private int[] searchForRole(String role, long timeout)
		throws RoleException
	{
		if(timeout==0)
			return searchForRole(role);

		int count, numReg;
		Registration tmp;
		int[] regArray;
		long finalTime, residualTime;

		finalTime=System.currentTimeMillis()+timeout;
		for(;;)
		{
			numReg=0;
			for(count=1; count<maxRegs; count++)
			{
				tmp=registrations[count];
				if((tmp!=null) && tmp.getRole().equals(role))
					numReg++;
			}
			residualTime=finalTime-System.currentTimeMillis();
			if(numReg!=0 || residualTime<=0)
				break;
			try
			{
				mutex.wait(residualTime);
			}
			catch (InterruptedException ie)
			{
			}
		}
		regArray=new int[numReg];
		numReg=0;
		for(count=1; count<maxRegs; count++)
		{
			tmp=registrations[count];
			if((tmp!=null) && tmp.getRole().equals(role))
			{
				regArray[numReg]=count;
				numReg++;
			}
		}
		return regArray;
	}


	/**
	 * Processes command "searchForRoleNoWait".
	 * @see rolesystem.core.RoleSystem#searchForRoleNoWait
	 */
	private int[] searchForRoleNoWait(String role)
		throws RoleException
	{
		int count, numReg;
		Registration tmp;
		int[] regArray;

		numReg=0;
		for(count=1; count<maxRegs; count++)
		{
			tmp=registrations[count];
			if((tmp!=null) && tmp.getRole().equals(role))
				numReg++;
		}
		regArray=new int[numReg];
		numReg=0;
		for(count=1; count<maxRegs; count++)
		{
			tmp=registrations[count];
			if((tmp!=null) && tmp.getRole().equals(role))
			{
				regArray[numReg]=count;
				numReg++;
			}
		}
		return regArray;
	}


	/**
	 * Processes command "dismiss".
	 * Dismisses a registration, clearing the corresponding entry in the
	 * internal array.
	 * @param id Identifier of registration to dismiss.
	 * @see rolesystem.core.RoleRegistration#dismiss
	 */
	private void dismiss(int id)
	{
		registrations[id]=null;
	}


	/**
	 * Processes command "doAction".
	 * Translates an action into an event and sends it to the proper
	 * destination.
	 * @param senderID Identifier of the action sender.
	 * @param sender Instance of Registration representing the action
	 * sender.
	 * @param action Action to process.
	 * @see rolesystem.core.RoleRegistration#doAction
	 */
	private void doAction(int senderID, Registration sender,
		RoleAction action) throws RoleException
	{
		int addresseeID=action.getAddressee();

		if( (addresseeID>0) && (addresseeID<maxRegs) )
		{
			Registration addressee=registrations[addresseeID];

			if( addressee!=null )
			{
				if(!permissionMatrix.checkPermission(
					sender.getRole(), addressee.getRole()))
					throw new RoleException(RoleException.NOT_ALLOWED);
				RoleMsg msgR=new RoleMsg(addresseeID,
					new RoleEvent(action.getName(), senderID,
					sender.getRole(), action.getContent()));
				ACLMessage msgACL=new ACLMessage(ACLMessage.REQUEST);

				msgACL.addReceiver(addressee.getAgent());
				try
				{
					msgACL.setContentObject(msgR);
				}
				catch(java.io.IOException ioe)
				{
					System.err.println(ioe);
					throw new RoleException();
				}
				myAgent.send(msgACL);
				return;
			}
		}
		throw new RoleException(RoleException.NOT_VALID_ADD);
	}


	/**
	 * This class represents a registration made by the server.
	 * Instances of this class are constant, once created they cannot be
	 * changed.
	 */
	private static class Registration
	{
		/**
		 * Role assigned to this registration.
		 */
		private String role;

		/**
		 * Agent-identifier of agent this registration is assigned to.
		 */
		private AID agent;


		/**
		 * @param role Role assigned to this registration.
		 * @param agent Agent-identifier of agent this registration is
		 * assigned to.
		 */
		public Registration(String role, AID agent)
		{
			this.role=role;
			this.agent=agent;
		}


		/**
		 * Returns the role assigned to this registration.
		 * @return The role assigned to this registration.
		 */
		public String getRole()
		{
			return role;
		}


		/**
		 * Returns the agent-identifier of agent this registration is
		 * assigned to.
		 * @return The agent-identifier of agent this registration is
		 * assigned to.
		 */
		public AID getAgent()
		{
			return agent;
		}
	}


	/**
	 * An instance of this class dispatches a received message to the
	 * method processMsg.
	 * The received message must be specified in construction, then the
	 * "run" method do the job. This allow to serve a message in a new
	 * thread.
	 */
	private class MessageDispatcher implements Runnable
	{
		/**
		 * Received message to process.
		 */
		private ACLMessage receivedMsg;


		/**
		 * @param receivedMsg Received message to process.
		 */
		public MessageDispatcher(ACLMessage receivedMsg)
		{
			this.receivedMsg=receivedMsg;
		}


		public void run()
		{
			processMsg(receivedMsg);
		}
	}
}
